package top.superflow.flow.load

import java.io.File
import java.util.regex.Pattern

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import top.superflow.common.LocalFlowConstant
import top.superflow.common.LocalFlowConstant._
import top.superflow.flow.config.template.TemplateUtils
import top.superflow.flow.load.factory.LoaderFactory
import top.superflow.json.util.JsonUtils
import top.superflow.lang.StringUtils
import top.superflow.log.Loggable
import top.superflow.utils.FileUtils

import scala.collection.mutable
import scala.collection.mutable.{Map, Set}


@Component
class LoadManager extends Loggable {
  
  @Autowired
  var flowConfiguration : GlobalFlowConfiguration = _
  
  private val flowFolder : String = FlowPathUtils.getResourcePath("flow/")
  
  private val validInnerConfFilePattern = "(.*)" + StringUtils.replaceOnce(LOCAL_FLOW_CONFIG_FLOW_FILE_SUFFIX, ".", "\\.") + "$|" +
                                          "(.*)" + StringUtils.replaceOnce(LOCAL_FLOW_CONFIG_PROCESS_FILE_SUFFIX, ".", "\\.") + "$|" +
                                          "(.*)" + StringUtils.replaceOnce(LOCAL_FLOW_CONFIG_PROCESS_PROVIDER_FILE_SUFFIX, ".", "\\.") + "$|" +
                                          "(.*)" + StringUtils.replaceOnce(LOCAL_FLOW_CONFIG_RESOURCE_FILE_SUFFIX, ".", "\\.") + "$|" +
                                          "(.*)" + StringUtils.replaceOnce(LOCAL_FLOW_CONFIG_MAPPER_FILE_SUFFIX, ".", "\\.") + "$|" +
                                          "(.*)" + StringUtils.replaceOnce(LOCAL_FLOW_CONFIG_TEMPLATE_FILE_SUFFIX, ".", "\\.") + "$"

  private val flowConfigFiles = Map[String, Set[String]]()


  def load : GlobalFlowConfiguration = {
    logger.info("Start to load flow configuration under folder:[{}]", flowFolder)
    val flowRootFolder = new File(flowFolder)
    
    if(!flowRootFolder.isDirectory()){
      logger.error("The root folder [{}] of the flow is not correct, please correct it. Exit now.", flowFolder)
      System.exit(-1)
    }
    iterateFolder(flowRootFolder)
    loadConfigInOrder
    flowConfiguration

  }
  
  
  private def loadConfigInOrder = {
    for((fileType, filePaths) <-  flowConfigFiles){
      loadTemplateConfiguration(fileType, filePaths)
    }

    for((fileType, filePaths) <-  flowConfigFiles){
      loadFlowElementConfiguration(fileType, filePaths)
    }

    for(flowConfigFile <- flowConfigFiles.get(LOCAL_FLOW_CONFIG_KEY_FLOW).get){
      GlobalFlowConfiguration.getInstance.add(LoaderFactory.getLoader(flowConfigFile).load(flowConfigFile))
    }
  }

  private def getConfigWithTemplate(flowConfigFile: String) : String = {
    val configJson = FileUtils.readText(FileUtils.getFile(flowConfigFile))
    val groupAndKey = JsonUtils.getField(configJson, LocalFlowConstant.CONFIG_FIELD_TEMPLATE)
    val configGroup = JsonUtils.getField(configJson, LocalFlowConstant.CONFIG_FIELD_GROUP)
    val templateGroup = TemplateUtils.getTemplateGroup(groupAndKey, configGroup, StringUtils.COLON)
    val templateKey = TemplateUtils.getTemplateId(groupAndKey, StringUtils.COLON)
    val template = GlobalFlowConfiguration.getInstance.getTemplate(templateGroup, templateKey)
    JsonUtils.merge(template, configJson)
  }

  private def loadTemplateConfiguration(fileType: String, filePaths: mutable.Set[String]) : Unit = {
    if (!StringUtils.equals(fileType, LOCAL_FLOW_CONFIG_KEY_TEMPLATE)) {
      return;
    }

    for (file <- filePaths) {
      val configLoader = LoaderFactory.getLoader(file)

      val config = configLoader.load(file)

      GlobalFlowConfiguration.getInstance.add(config)
    }
  }

  private def loadFlowElementConfiguration(fileType: String, filePaths: mutable.Set[String]) : Unit = {
    if (StringUtils.equals(fileType, LOCAL_FLOW_CONFIG_KEY_FLOW) || StringUtils.equals(fileType, LOCAL_FLOW_CONFIG_KEY_TEMPLATE)) {
      return;
    }

    for (file <- filePaths) {
      val configJson = getConfigWithTemplate(file)

      val configLoader = LoaderFactory.getLoader(file)

      val config = configLoader.load(configJson)

      GlobalFlowConfiguration.getInstance.add(config)
    }
  }

  private def iterateFolder(file : File) {
    if(file == null) return;
    
    for(file <- file.listFiles()) {
      findAllConfigFiles(file)
    }
    
  }

  private def findAllConfigFiles(file : File) : Unit= {
    if(file == null) return
   
    val isFile = file.isFile()
    
    if(isFile && !isValidFilePattern(file)) return
    
    if(isFile){
      storeConfigFiles(file)
    }else{
      iterateFolder(file)
    }
  }
  
  def storeConfigFiles(file : File) = {
    val filePath = file.getAbsolutePath
    filePath match {
      case fname if LoadManager.processProviderFilePattern.matcher(fname).find() =>{
        ensurePut(LOCAL_FLOW_CONFIG_KEY_PROCESS_PROVIDER, filePath)
      }

      case fname if LoadManager.processFilePattern.matcher(fname).find() =>{
        ensurePut(LOCAL_FLOW_CONFIG_KEY_PROCESS, filePath)
      }

      case fname if LoadManager.resourceFilePattern.matcher(fname).find() =>{
        ensurePut(LOCAL_FLOW_CONFIG_KEY_RESOURCE, filePath)
      }

      case fname if LoadManager.mapperFilePattern.matcher(fname).find() =>{
        ensurePut(LOCAL_FLOW_CONFIG_KEY_MAPPER, filePath)
      }

      case fname if LoadManager.flowFilePattern.matcher(fname).find() =>{
        ensurePut(LOCAL_FLOW_CONFIG_KEY_FLOW, filePath)
      }

      case fname if LoadManager.templateFilePattern.matcher(fname).find() =>{
        ensurePut(LOCAL_FLOW_CONFIG_KEY_TEMPLATE, filePath)
      }
    }
  }

  def ensurePut(fileType : String, filePath : String) = {
    var configs = flowConfigFiles.get(fileType)
    if(configs == None){
      flowConfigFiles += (fileType -> Set[String]())
      configs = flowConfigFiles.get(fileType)
    }
    configs.get += filePath
  }
  
  def getConfiguration : GlobalFlowConfiguration = flowConfiguration
  
  private def isValidFilePattern(file : File) : Boolean = {
    if(file == null) return false;
    
    val validInnerConfigFilePattern = Pattern.compile(validInnerConfFilePattern);
    val matcher = validInnerConfigFilePattern.matcher(file.getName);
    if(matcher.find()) return true;

    logger.warn("The file [" + file + "] doesn't meet the pattern of [" + validInnerConfFilePattern + "]")
    false
  }
}

object LoadManager {

  val flowFilePattern = Pattern.compile("\\w+.flow.json")

  val templateFilePattern = Pattern.compile("\\w+.template.json")
  
  val resourceFilePattern = Pattern.compile("\\w+.resource.json")

  val processFilePattern = Pattern.compile("\\w+.process.json")

  val mapperFilePattern = Pattern.compile("\\w+.mapper.json")

  val processProviderFilePattern = Pattern.compile("\\w+.processprovider.json")
}